/*
 * Copyright 2013 Aliyun.com All right reserved. This software is the
 * confidential and proprietary information of Aliyun.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Aliyun.com .
 */
package com.mtauto.service;

import static java.text.MessageFormat.format;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mtauto.exception.MtautoException;
import com.mtauto.exception.MtautoException.MtautoCode;
import com.mtauto.model.Apk;

/**
 * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> Nov 19, 2013
 */
public class AdbService {

    public static final String  CMD_GETSERIALNO          = "get-serialno";
    public static final String  CMD_SHELL_GETPROP        = "-s {0} shell getprop";
    public static final String  CMD_SHELL_DUMPSYS_WINDOW = "-s {0} shell dumpsys window";
    public static final String  CMD_SHELL_NETCFG         = "-s {0} shell netcfg";
    public static final String  CMD_SHELL_LIST_PACKAGES  = "-s {0} shell pm list packages";
    public static final String  CMD_INSTALL              = "-s {0} install -r {1}";
    public static final String  CMD_UNINSTALL            = "-s {0} uninstall {1}";
    public static final String  CMD_SHELL_UIAUTOMATOR    = "-s {0} shell uiautomator dump /dev/tty";
    public static final String  CMD_REBOOT               = "-s {0} reboot";

    private static final Logger LOGGER                   = LoggerFactory
                                                                 .getLogger(AdbService.class);

    private static final String SPLIT_NEWLINE            = "\r\n";
    private static final String SPLIT_BLANKSPACE         = "\\s+";
    private static final String SPLIT_COLON              = ":";

    private String              adb;
    private long                timeout                  = 10000L;                                  //default timeout is 10 seconds.
    private long                writeTimeout             = 60000L * 2;                              // default write timeout is 120 seconds.

    private ExecutorService     threadPool               = Executors.newFixedThreadPool(50);
    private long                sequence;
    private Map<Long, Process>  procs                    = new ConcurrentHashMap<Long, Process>(
                                                                 50 * 2);
    private Process             recordProcess;
    private Process             monitorProcess;

    private ApkService          apkService;

    public void setApkService(ApkService apkService) {
        this.apkService = apkService;
    }

    public void destroy() {
        for (Process proc : procs.values()) {
            proc.destroy();
        }
        threadPool.shutdown();
    }

    public void stopRecordThread() {
        try {
            if (recordProcess != null)
                recordProcess.destroy();
            //            if (monitorProcess != null)
            //                monitorProcess.destroy();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                Collection<Apk> apks = apkService.getApks();
                String p1 = "";
                for (Apk apk : apks) {
                    if (!apk.getTitle().equals("cafeTest")) {
                        p1 = apk.getName();
                        break;
                    }
                }
                ApkService.targetPackage = p1;
                ApkService.execCMD(adb + " pull /data/data/" + ApkService.targetPackage
                        + "/files/CafeReplay.java  " + apkService.getMtautoHome());

                writeReplayFile();
                apkService.genRecordApk(ApkService.targetPackage, "");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    private void writeReplayFile() throws Exception {
        String file = apkService.getMtautoHome() + "/CafeReplay.java";
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        FileOutputStream fos = new FileOutputStream(apkService.getMtautoHome()
                + "/cafeTest/src/org/codeanywhere/cafe/test/CafeReplay.java");
        String line = null;
        while ((line = br.readLine()) != null) {
            line = line.replace("com.example.demo.test", "org.codeanywhere.cafe.test");
            if (line.contains("public CafeReplay()")) {
                fos.write("@SuppressWarnings(\"unchecked\")".getBytes());
                fos.write("\r\n".getBytes());
            }
            if (line.contains("@Override")) {
                line = "";
            }
            fos.write(line.getBytes());
            fos.write("\r\n".getBytes());
        }
        fos.flush();
        fos.close();
        br.close();
    }

    public void startRecordThread() {
        try {
            recordProcess = ApkService
                    .asyncExecCMD(adb
                            + " shell am instrument -e class org.codeanywhere.cafe.test.TestCafeRecord#test_sample -w org.codeanywhere.cafe.test/com.baidu.cafe.CafeTestRunner");

            if (monitorProcess == null) {

                monitorProcess = ApkService.asyncExecCMD(adb
                        + " logcat -v time ViewRecorder:I RecorderCode:I LocalLib:I *:S");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void startReplayThread() {
        try {
            recordProcess = ApkService
                    .asyncExecCMD(adb
                            + " shell am instrument -e class org.codeanywhere.cafe.test.CafeReplay#testRecorded -w org.codeanywhere.cafe.test/com.baidu.cafe.CafeTestRunner");

            if (monitorProcess == null) {
                monitorProcess = ApkService.asyncExecCMD(adb
                        + " logcat -v time ViewRecorder:I RecorderCode:I LocalLib:I *:S");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public String[] getSerialnos() {
        String resp = execute(CMD_GETSERIALNO);
        LOGGER.debug("getSerialno#resp is {}", new Object[] { resp });
        if (resp == null || resp.isEmpty() || resp.contains("unknown")) {
            return new String[0];
        }
        String[] tmp = resp.split(SPLIT_NEWLINE);
        String[] serialnos = new String[tmp.length];
        for (int i = 0; i < tmp.length; i++) {
            serialnos[i] = tmp[i].trim();
        }
        return serialnos;
    }

    public String getProduct(String serialno) {
        String resp = execute(format(CMD_SHELL_GETPROP, serialno));
        LOGGER.debug("getProduct#resp is {}", new Object[] { resp });
        if (resp == null || !resp.contains("ro.product")) {
            return null;
        }
        Map<String, String> props = new HashMap<String, String>();
        for (String value : resp.split(SPLIT_NEWLINE)) {
            String[] kv = value.split(SPLIT_COLON);
            if (kv.length == 2) {
                String k = kv[0].trim();
                String v = kv[1].trim();
                props.put(k.substring(1, k.length() - 1), v.substring(1, v.length() - 1));
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append(props.get("ro.product.brand")).append("#");
        sb.append(props.get("ro.product.device")).append("#");
        sb.append(props.get("ro.product.model"));
        return sb.toString();
    }

    public String getDisplay(String serialno) {
        String resp = execute(format(CMD_SHELL_DUMPSYS_WINDOW, serialno));
        LOGGER.debug("getDisplay#resp is {}", new Object[] { resp });
        if (resp == null || resp.isEmpty()) {
            return null;
        }
        for (String value : resp.split(SPLIT_NEWLINE)) {
            if (value.contains("mRestrictedScreen")) {
                return value.trim().split(SPLIT_BLANKSPACE)[1].trim();
            }
        }
        return null;
    }

    public String getNetinfo(String serialno) {
        String resp = execute(format(CMD_SHELL_NETCFG, serialno));
        LOGGER.debug("getNetinfo#resp is {}", new Object[] { resp });
        if (resp == null || resp.isEmpty()) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (String value : resp.split(SPLIT_NEWLINE)) {
            String[] v = value.split(SPLIT_BLANKSPACE);
            if (v[2].contains("0.0.0.0") || v[2].contains("127.0.0.1")) {
                continue;
            }
            if (v[2].indexOf("/") < 0) {
                sb.append(v[0].trim()).append(":").append(v[2].trim()).append(";");
            } else {
                sb.append(v[0].trim()).append(":").append(v[2].substring(0, v[2].indexOf("/") - 1))
                        .append(";");
            }
        }
        if (sb.toString().isEmpty()) {
            return null;
        }
        return sb.substring(0, sb.length() - 1).toString();
    }

    public String[] getPackages(String serialno) {
        String resp = execute(format(CMD_SHELL_LIST_PACKAGES, serialno));
        LOGGER.debug("getNetinfo#resp is {}", new Object[] { resp });
        if (resp == null || resp.isEmpty()) {
            return new String[0];
        }
        String[] values = resp.split(SPLIT_NEWLINE);
        String[] packages = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            packages[i] = values[i].split(SPLIT_COLON)[1].trim();
        }
        return packages;
    }

    public boolean install(String serialno, String pkg) {
        String resp = execute(format(CMD_INSTALL, new Object[] { serialno, pkg }), writeTimeout);
        LOGGER.debug("install#resp is {}", new Object[] { resp });
        return resp != null && resp.contains("Success");
    }

    public boolean uninstall(String serialno, String pkg) {
        String resp = execute(format(CMD_UNINSTALL, new Object[] { serialno, pkg }), writeTimeout);
        LOGGER.debug("install#resp is {}", new Object[] { resp });
        return resp != null && resp.contains("Success");
    }

    public String uiautomator(String serialno) {
        String resp = execute(format(CMD_SHELL_UIAUTOMATOR, new Object[] { serialno }));
        LOGGER.debug("uiautomator#resp is {}", new Object[] { resp });
        if (resp != null && resp.contains("UI hierchary dumped to: /dev/tty")) {
            return resp.trim().split("UI hierchary dumped to: /dev/tty")[0].trim();
        }
        return null;
    }

    public void reboot(String serialno) {
        execute(format(CMD_REBOOT, serialno));
    }

    protected String execute(final String cmd) {
        return execute(cmd, timeout);
    }

    protected String execute(final String cmd, long timeout) {
        final long seq = sequence++;
        FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
            public String call() throws Exception {
                return doExecute(seq, cmd);
            }
        });
        try {
            threadPool.execute(task);
            return task.get(timeout, TimeUnit.MILLISECONDS);
        } catch (MtautoException e) {
            throw e;
        } catch (Exception e) {
            MtautoCode code = e.getClass().equals(TimeoutException.class) ? MtautoCode.ADB_TIMEOUT
                    : MtautoCode.ADB_FAILED;
            throw new MtautoException(code, "adb cmd is: " + cmd, e);
        } finally {
            task.cancel(true);
            Process proc = procs.get(seq);
            if (proc != null) {
                proc.destroy();
            }
            procs.remove(seq);
        }
    }

    protected String doExecute(long seq, String cmd) throws Exception {
        StringBuilder sb = new StringBuilder();
        byte[] buffer = new byte[1024];
        try {
            LOGGER.debug("execute#cmd is {}{}", new Object[] { adb, cmd });
            procs.put(seq, Runtime.getRuntime().exec(adb + cmd));
            BufferedInputStream in = new BufferedInputStream(procs.get(seq).getInputStream());
            int len;
            while ((len = in.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len));
            }
            in.close();
            return sb.toString();
        } finally {
            procs.get(seq).destroy();
            procs.remove(seq);
        }
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public void setWriteTimeout(long writeTimeout) {
        this.writeTimeout = writeTimeout;
    }

    public void setAdb(String adb) {
        File f = new File(adb);
        if (!f.exists()) {
            throw new MtautoException(MtautoCode.ADB_NOT_EXISTED);
        }
        this.adb = adb.trim() + " ";
    }

    public static void main(String[] args) {
        AdbService adbService = new AdbService();
        try {
            adbService
                    .setAdb("/Users/stone/local/installed/adt-bundle-mac-x86_64/sdk/platform-tools/adb");
            System.out.println(adbService.getSerialnos()[0]);
            //System.out.println(adbService.getProduct("7D45HQLZDA5DCQOZ"));
            //System.out.println(adbService.getDisplay("7D45HQLZDA5DCQOZ"));
            //System.out.println(adbService.getNetinfo("7D45HQLZDA5DCQOZ"));
            //System.out.println(Arrays.toString(adbService.getPackages("7D45HQLZDA5DCQOZ")));
            //System.out.println(adbService.install("7D45HQLZDA5DCQOZ", "/Users/stone/Downloads/XuanYuan.apk"));
            //System.out.println(adbService.uninstall("7D45HQLZDA5DCQOZ", "com.alibaba.newxyj"));
            //System.out.println(adbService.uiautomator("7D45HQLZDA5DCQOZ"));

            //adbService.setTimeout(10L);
            //adbService.getSerialnos();

            //adbService.reboot("7D45HQLZDA5DCQOZ");
        } catch (Exception e) {
            if (e instanceof MtautoException) {
                MtautoException me = (MtautoException) e;
                System.out.println(me.getCode());
            }
            System.out.println(e);
        } finally {
            adbService.destroy();
        }
    }

}
